﻿#region License Information

/* •——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————•
   | Copyright Aptillon, Inc.                                                                                        |
   | This file is part of the CKS:API project - cksapi.codeplex.com                                                       |
   | Usage, modification and distribution subject to the license terms available here: http://cksapi.codeplex.com/license |
   |                                                                                                                      |
   | Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,             |
   | either express or implied.                                                                                           |
   |                                                                                                                      |
   | THIS MESSAGE MAY NOT BE LEGALLY REMOVED                                                                              |
   •——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————• */

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using CKS.API.Contracts;
using Microsoft.SharePoint;
using System.Reflection;
using CKS.API.Exceptions;
using System.Web.UI;
using CKS.API.Extensions;
namespace CKS.API.Logging
{

    public class CKSSandboxLogger : CKSLogger
    {

        #region Constants

        private const int KeepLogForDays = 7;

        private const string SandboxLogListDescription = "List for storing log entries made from sandbox solutions";

        private const string SandboxLogListName = "SandboxLog";

        #endregion

        #region Fields

        private CodeContract _contract;

        private List<string> logEntries;

        private int currentLogLevel;

        #endregion

        #region Constructors

        public CKSSandboxLogger(SPWeb rootWeb, bool shouldDispose)
            : base(rootWeb, shouldDispose)
        {
            currentLogLevel = GetLogLevel();
        }

        #endregion

        #region Properties


        #endregion

        #region Public Methods

        public override void BeginScope()
        {
            if (Check.ThatIsNotNull(logEntries))
            {
                //Write anything currently waiting to be logged.
                EndScope();
            }
            logEntries = new List<string>();
        }

        public override void CatchUnhandledExceptions()
        {
            base.CatchUnhandledExceptions();
        }

        public override void EndScope()
        {
            if (null == logEntries || logEntries.Count <= 0)
                return;

            CommitEntries();
            logEntries.Clear();
        }



        #endregion

        #region Internal Methods

        protected override void NotifyAdministrator(string alertId, string alertBody)
        {
            //TODO: Move implementation from Farm to base class and make not abstract

        }

        protected override void WriteToDeveloperDashboard(string message)
        {
            WriteToDeveloperDashboard(message, LogToDeveloperDashboardDummyMethod);
        }

        protected override void WriteToDeveloperDashboard(string message, Action action)
        {

            /*
             * TODO: Use Javascript to write to the developer dashboard
             */
        }

        #endregion

        #region Private Methods

        public int GetLogLevel()
        {
            object logLevel = new object();
            if (null != SPContext.Current)
            {
                try
                {
                    SPFolder rootFolder = SPContext.Current.Site.RootWeb.Lists.TryGetList(SandboxLogListName).RootFolder;
                    logLevel = rootFolder.GetProperty("CKSAPI_LogLevel");


                }
                catch
                { }

            }

            if (null != logLevel)
            {

                int retVal = -1;
                if (
                    (int.TryParse(logLevel.ToString(), out retVal))
                    && (retVal > -1)
                    )
                {
                    return retVal;
                }
            }
            //by default, exclude Informational messages
            return 1;
        }


        protected override void Log(string message, CKSLoggingCategory category, LoggingSeverity severity)
        {
            //Add to our in-memory list of things to log if the severity of this entry is equal to or higher than the current log level configured by the admin
            if ((int)severity >= currentLogLevel)
            {
                bool WriteImmediately = false;
                if (null == logEntries)
                {
                    WriteImmediately = true;
                    BeginScope();
                }

                logEntries.Add(string.Format("{0}: {1}: {2}<br />", string.Format("{0:HH:mm:ss:FFFFFFF}", DateTime.Now), category, message));

                if (WriteImmediately)
                {
                    EndScope();
                }
            }
        }

        // Documentation:
        //Unfortunately, can't do this...by the time the destructor is called, SPContext.Current is null and we need that to access the logging list.
        // This means that the EndScope method MUST be called explicitly.

        //~CksSandboxLoggingService()
        //{
        //    EndScope();
        //}

        private void CommitEntries()
        {
            Contract.RequiresThat(CodeContract.ValidateIsNotNull(CurrentWeb));
            try
            {

                SPList logList = CurrentWeb.Lists.TryGetList(SandboxLogListName);
                if (Check.ThatIsNull(logList))
                {
                    //create the logging list
                    InitializeSpecificEnvironment();
                    logList = CurrentWeb.Lists.TryGetList(SandboxLogListName);
                }


                if (Check.ThatIsNotNull(logList))
                {
                    bool foundExisting = false;
                    StringBuilder sb = new StringBuilder();
                    SPListItem logItem = GetUserLogItemOrNewLogItem(logList, out foundExisting);

                    if (!foundExisting)
                    {
                        logItem["Title"] = CurrentWeb.CurrentUser.LoginName;


                    }

                    logEntries.ForEach(oneEntry => sb.Append(oneEntry));
                    logItem[Constants.LogFieldName] = sb.ToString();

                    CurrentWeb.AllowUnsafeUpdates = true;
                    logItem.Update();
                    CurrentWeb.AllowUnsafeUpdates = false;

                }

            }
            catch
            {
                //swallow errors so we don't throw any errors while logging
            }

        }

        protected override void InitializeSpecificEnvironment()
        {
            //Called from base class InitializeSiteCollectionEnvironment to allow us to set up the specific child environment (sandbox) after the common stuff is done
            Contract.RequiresThat(CodeContract.ValidateIsNotNull(CurrentWeb));

            object locker = new object();
            SPList logList = CurrentWeb.Lists.TryGetList(SandboxLogListName);
            if (Check.ThatIsNull(logList))
            {
                lock (locker)
                {
                    logList = CurrentWeb.Lists.TryGetList(SandboxLogListName);
                    if (Check.ThatIsNull(logList))
                    {

                        CreateSandboxLogList();
                    }
                }
            }
        }

        private void CreateSandboxLogList()
        {
            SPList logList = CurrentWeb.Lists.GetList(CurrentWeb.Lists.Add(SandboxLogListName, SandboxLogListDescription, SPListTemplateType.GenericList), false);
            SPFieldCollection fields = logList.Fields;
            fields.Add(Constants.LogFieldName, SPFieldType.Note, true);
            SPFieldMultiLineText logField = fields.GetFieldByInternalName(Constants.LogFieldName) as SPFieldMultiLineText;
            logField.AppendOnly = true;
            logField.AllowDeletion = false;
            logField.RichText = true;
            logField.RichTextMode = SPRichTextMode.Compatible;
            logField.Update();

            SPView defaultView = logList.DefaultView;
            SPViewFieldCollection viewFields = defaultView.ViewFields;
            viewFields.Add(logField);
            defaultView.Update();

            logList.EnableVersioning = true;
            logList.Update();

            BeginScope();
            Write("Logging list has been initialized", CKSLoggingCategory.General, LoggingSeverity.Information);
            EndScope();
        }

        private int GetKeepLogForDays()
        {
            //ToDo: Read from a configuration setting
            return KeepLogForDays;
        }

        private SPListItem GetUserLogItemOrNewLogItem(SPList logList, out bool foundExisting)
        {
            #region Contract
            if (Check.ThatIsNull(logList))
            {

                foundExisting = false;
                return null;
            }
            #endregion

            SPQuery qry = new SPQuery
            {
                Query = string.Format(
                    @"   <Where>
                                              <Eq>
                                                 <FieldRef Name='Title' />
                                                 <Value Type='Text'>{0}</Value>
                                              </Eq>
                                           </Where>",
                    CurrentWeb.CurrentUser.LoginName),
                RowLimit = 1,
                ViewFields = "<FieldRef Name='Title' /><FieldRef Name='Log' />",
            };
            SPListItemCollection coll = logList.GetItems(qry);
            if (null != coll
                && coll.Count > 0)
            {
                //Automatically clean up if log item is older than "KeepLogForDays"
                if (((DateTime)coll[0]["Modified"]).CompareTo(DateTime.Now.AddDays(0 - GetKeepLogForDays())) < 0)
                {
                    coll[0].Delete();
                    logList.ParentWeb.AllowUnsafeUpdates = true;
                    logList.Update();
                    logList.ParentWeb.AllowUnsafeUpdates = false;

                }
                else
                {
                    foundExisting = true;
                    return coll[0];
                }

            }
            //TODO: Create new items in a folder hierarchy to avoid perf problems?
            foundExisting = false;
            return logList.AddItem();
        }

        private void LogToDeveloperDashboardDummyMethod()
        {
            //do nothing...this is an empty method to allow us to write messages to the Developer Dashboard whenever we want to.

        }

        #endregion

    }

}

